﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Super_Wall_Breaker.Sources.GameElements.Balls;
using Super_Wall_Breaker.Sources.GameEngine;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

namespace Super_Wall_Breaker.Sources.TeamElements.Players
{
    /// <summary>
    /// This class represents a player which is controlled by an artificial intelligence.
    /// </summary>
    public class AIPlayer : Player
    {
        private const float TOLERANCE_FACTOR = 0.1f; // This constant is used because the game's metric system is in pixels but it's a little bit to precise (when we calculate trajectories) so we accept values in intervals.

        Random _random;
        private List<Ball> _balls;// The list of balls in the game.
        private int _targetX; // The X coordonate where the bat should be to hit the ball.
        private int _previousUpdateTargetX;
        private int _impactPoint;//The point on the bat where we want to hit the ball @see DefineImpactPoint method.
        private int _tolerance; // The tolerance formula @see AIPlayer constructor.
        private double _minNumberOfStep; //The number of steps necesseray to reach the bat's Y axis

        /// <summary>
        /// This constructor makes a new AIPlayer
        /// </summary>
        /// <param name="balls">The list of balls in the game</param>
        /// <param name="batSpeed">The speed of the bats.</param>
        /// <param name="teamIndex">The teamIndex of this IAPlayer</param>
        public AIPlayer(List<Ball> balls, TeamIndex teamIndex, Vector2 position, Vector2 batSpeed, Rectangle? sourceRectangle)
            : base(teamIndex, position, batSpeed, sourceRectangle)
        {
            this._balls = balls;
            this._targetX = (int) (this._bat.Position.X + this._bat.SourceRectangle.Value.Width / 2);
            this._previousUpdateTargetX = this._targetX;
            this._impactPoint = this._targetX;
            this._tolerance = (int) (this._bat.SourceRectangle.Value.Width * AIPlayer.TOLERANCE_FACTOR);
            this._random = new Random();

        }

        /// <summary>
        /// This function is called by the class GameScreenPlay at each loop of the game.
        /// </summary>
        /// <param name="gameTime">A snap shot of the game time value</param>
        public override void Update(GameTime gameTime)
        {
            this._minNumberOfStep = 0;
            
            foreach (Ball ball in this._balls)
            {

                if (this.BallIsCloseEnough(ball) && this.BallIsComing(ball))
                {

                    // x = my + xAxisAtOrigin    m= dx/dy We use equation of lines to calculate the trajectory of the ball
                    float x = ball.Position.X + ball.SourceRectangle.Value.Width / 2;
                    float m = ball.Speed.X / ball.Speed.Y;
                    float y = ball.Position.Y;
                    float xAxisAtOrigin = x - m * y;
                    float decisiveAxisY = this._bat.Position.Y;

                    if (this._teamIndex == TeamIndex.top)
                    {
                        decisiveAxisY += this._bat.SourceRectangle.Value.Height;
                    }

                    int xOnyDecisiveAxis = (int) (m * decisiveAxisY + xAxisAtOrigin);

                    double d = Math.Sqrt(Math.Pow(ball.Position.X - xOnyDecisiveAxis, 2) + Math.Pow(ball.Position.Y - decisiveAxisY, 2));//we use vector's norm formula to calculate the distance between the ball and the bat's Y axis
                    double t = d * (Math.Sqrt(Math.Pow(ball.Speed.X, 2) + Math.Pow(ball.Speed.Y, 2)));// The number of steps necesseray to reach the bat's Y axis

                    if ((this._minNumberOfStep > t &&  this._targetX != xOnyDecisiveAxis) || this._minNumberOfStep == 0)
                    {
                        this._minNumberOfStep = t;
                        this._targetX = xOnyDecisiveAxis;

                    }
                }
            }

            this.DefineImpactPoint(this._targetX);
            this.DefineNewSpeed(this._impactPoint);
            this._bat.Update(gameTime);
        }

        //This function checks if the ball is close enough to hunt it.
        private bool BallIsCloseEnough(Ball ball)
        {
            bool returnValue = false;

            if ((this._teamIndex == TeamIndex.bottom && ball.Position.Y > GlobalConfiguration.DEFAULT_SCREEN_HEIGHT / 2)
                || (this._teamIndex == TeamIndex.top && ball.Position.Y < GlobalConfiguration.DEFAULT_SCREEN_HEIGHT / 2))
            {
                returnValue = true;
            }

            return returnValue;
        }

        //This function checks if the ball is getting closer to this IAPlayer's bat.
        private bool BallIsComing(Ball ball)
        {
            bool returnValue = false;

            if (!ball.BallIsWaiting())
            {
                if ((this._teamIndex == TeamIndex.bottom && ball.Speed.Y > 0) || (this._teamIndex == TeamIndex.top && ball.Speed.Y < 0))
                {
                    returnValue = true;
                }
            }

            return returnValue;
        }

        //This function modifies the bat's speed.
        private void DefineNewSpeed(int impactPoint)
        {
            float middleXOfBat = this._bat.Position.X + this._bat.SourceRectangle.Value.Width / 2;

            if (middleXOfBat < impactPoint - this._tolerance)
            {
                this._bat.Speed = new Vector2(this._initialBatSpeed.X, this._bat.Speed.Y);
            }
            else if (middleXOfBat > impactPoint + this._tolerance)
            {
                this._bat.Speed = new Vector2(-this._initialBatSpeed.X, this._bat.Speed.Y);
            }
            else
            {
                this._bat.Speed = new Vector2(0, this._bat.Speed.Y);
            }
        }

        //This function defines an impact point on the bat, it aims to avoid hitting the ball with the middle of the bat every time.
        private void DefineImpactPoint(int targetX)
        {
            if ((targetX > this._previousUpdateTargetX )|| (targetX < this._previousUpdateTargetX ))
            {
                this._previousUpdateTargetX = targetX;
                this._impactPoint = (int) this._random.Next((int)(targetX - (this._bat.SourceRectangle.Value.Width / 2 - AIPlayer.TOLERANCE_FACTOR)),
                                                            (int)(targetX + this._bat.SourceRectangle.Value.Width / 2 - AIPlayer.TOLERANCE_FACTOR));
            }
        }
    }
}